{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Proof of Concept &mdash; embedding a Bokeh server in a Notebook\n",
    "\n",
    "This notebook shows an apprioach to embedding Bokeh server application inside Jupyter notebook. Note that this method has some potential drawbacks, and that in future releases, there will be new APIs to mitigate these drawbacks and streamline usage."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "from bokeh.layouts import column\n",
    "from bokeh.models import ColumnDataSource, Slider\n",
    "from bokeh.plotting import figure"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are various application handlers that can be used to build up Bokeh documents. For example, there is a `ScriptHandler` that uses the code from a `.py` file to produce Bokeh documents. This is the handler that is used when we run `bokeh serve app.py`. Here we are going to use the lesser-known `FunctionHandler`, that gets configured with a plain Python function to build up a document. \n",
    "\n",
    "Here is the function `modify_doc(doc)` that defines our app:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def modify_doc(doc):\n",
    "    x = np.linspace(0, 10, 1000)\n",
    "    y = np.log(x) * np.sin(x)\n",
    "\n",
    "    source = ColumnDataSource(data=dict(x=x, y=y))\n",
    "\n",
    "    plot = figure()\n",
    "    plot.line('x', 'y', source=source)\n",
    "\n",
    "    slider = Slider(start=1, end=10, value=1, step=0.1)\n",
    "\n",
    "    def callback(attr, old, new):\n",
    "        y = np.log(x) * np.sin(x*slider.value)\n",
    "        source.data = dict(x=x, y=y)\n",
    "    slider.on_change('value', callback)\n",
    "    \n",
    "    doc.add_root(column(slider, plot))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We take the function above and configure a `FunctionHandler` with it. Then we create an `Application` that uses handler. (It is possible, but uncommon, for Bokeh applications to have more than one handler.) The end result is that the Bokeh server will call `modify_doc` to build new documents for every new sessions that is opened."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from bokeh.application.handlers import FunctionHandler\n",
    "from bokeh.application import Application\n",
    "\n",
    "handler = FunctionHandler(modify_doc)\n",
    "app = Application(handler)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Bokeh server runs on a Tornado `IOLoop`. When we run `bokeh serve` at the command line, an `IOLoop` is created and started automatically. In this case we are going to piggy-back off an existing `IOLoop`, the one that the Jupyter Notebook has. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from tornado.ioloop import IOLoop\n",
    "loop = IOLoop.current()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Below is a convenience function for showing the resulting app inline in a Jupyter notebook. In the future, there will be an API built into Bokeh that does this. \n",
    "\n",
    "**WARNING** The function below leaks resources. It creates a new `Server` (with app and handler) every time it is executed, so re-executing some (or all) cells will leave stray Bokeh servers attached to the notebook `IOLoop`. For apps *without periodioc callbacks* this is probably often \"OK\", because the leftover server objects are effectively completely dormant when no events come in to them. But if an app has periodic callbacks, they will ***continue to run indefinitely***, which is probably undesirable. In this case, until there is proper Bokeh API to handle cleanup, it is recommended to run the code below explicitly, and \"keep track\" of any servers you create. Their sessions can be shut off by executing:\n",
    "```\n",
    "server.get_sessions()[0].destroy()\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def show_app(app, notebook_url=\"127.0.0.1:8888\"):\n",
    "    from IPython.display import HTML, display\n",
    "    from bokeh.embed import autoload_server\n",
    "    from bokeh.server.server import Server\n",
    "    \n",
    "    server = Server({'/': app}, io_loop=loop, port=0, host='*', allow_websocket_origin=[notebook_url])\n",
    "    server.start()\n",
    "    \n",
    "    script = autoload_server(model=None, url='http://127.0.0.1:%d' % server.port)\n",
    "    \n",
    "    display(HTML(script))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "show_app(app)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python [conda root]",
   "language": "python",
   "name": "conda-root-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.4.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
